// OpenGL Shader Material
// (c) jimon game studio

#ifndef JEH_CSHADERMATERIAL_OPENGL
#define JEH_CSHADERMATERIAL_OPENGL

#include "IRender.h"
#include "IShaderMaterial.h"
#include "CRender_OpenGL_Extensions.h"
#include "CShaderVariable_OpenGL.h"
#include "je3dTypes.h"
#include "IHardwareBufferArray.h"

#ifdef JE_RENDER_OPENGL

#include "ILogManager.h"

// TODO rewrite it without STL
//#define JE_SHADER_DEBUG

#ifdef JE_SHADER_DEBUG
#ifndef JE_DEBUG
#error Turn JE_SHADER_DEBUG off !
#endif
#endif

#ifdef JE_SHADER_DEBUG

#include <sstream>
#include <string>
#include <vector>

#endif

#define MAX_LOG_SIZE 1024

#include "jeOpenGLWorkMode.h"

namespace je
{
	namespace video
	{
		//! OpenGL Shader Material
		class CShaderMaterial_OpenGL:public je::video::IShaderMaterial
		{
		public:
			//! Shader Define
			struct ShaderDefine
			{
				//! Technique
				u8 Technique;

				//! Define Name
				jeStringc Name;
				
				//! Define Code
				jeStringc Define;
			};

			//! Shader Defines List Type
			typedef jeList<ShaderDefine*> ShaderDefinesListType;

			//! Shader Setup Uniform Data Type
			enum ShaderSetupUniformDataType
			{
				SSUDT_MATRIX_44F,
				SSUDT_MATRIX_33F,
				SSUDT_ARRAY_S32,
				SSUDT_ARRAY_F32,
				SSUDT_ARRAY_VECTOR_3DF,
				SSUDT_VECTOR_3DF,
				SSUDT_VECTOR_3DI,
				SSUDT_VECTOR_2DF,
				SSUDT_VECTOR_2DI
			};

			//! Shader Setup Uniform
			struct ShaderSetupUniform
			{
				GLint Location;
				GLhandleARB SP;
				ShaderSetupUniformDataType DataType;
				void * Data;
				u32 DataSize;
			};

			//! Shader Setup Uniform List Type
			typedef jeDynamicArray<ShaderSetupUniform*> ShaderSetupUniformListType;
		private:

			//! Current Render
			IRender * CurrentRender;

			//! Current Extensions
			CRender_OpenGL_Extensions * Extensions;

			//! Shader Pointers
			//! needed for global techniques
			struct ShaderPointers
			{
				GLhandleARB VS,FS,SP;
				GLint AttributeLocations[JE_MAX_SHADER_VERTEX_LAYERS];
				GLint ModelViewProjectionMatrix,ModelViewMatrix,ModelMatrix,NormalMatrix,TextureMatrix;
				ShaderPointers()
					:VS(0),FS(0),SP(0)
				{
				}
			};

			//! Shader Pointers Array
			ShaderPointers Pointers[JE_MAX_SHADER_GLOBAL_TECHNIQUES];

			//! Shader Programs Array
			GLhandleARB SPForShaderVariables[JE_MAX_SHADER_GLOBAL_TECHNIQUES];

			//! Original Source, needed for rebuild
			jeStringc OriginalShaderSource;

			//! Shader Variables List
			typedef jeList<CShaderVariable_OpenGL*> ShaderVariablesListType;
			ShaderVariablesListType ShaderVariablesList;

			//! Uniform Cache
			ShaderSetupUniformListType ShaderSetupUniformList;

			//! Currently Binded
			u1 CurrentlyBinded;

			//! Ignore Location Error For New Variables
			u1 IgnoreLocationErrorForNewVariables;

			//! Compile Shader Source
			u1 CompileShaderSource(GLhandleARB Shader,const c8 * ShaderSource)
			{
				#ifdef JE_SHADER_DEBUG
					std::istringstream InputStringStream(ShaderSource);
	
					std::vector<std::string> SourceLines;
					for(std::string Line;std::getline(InputStringStream,Line);)
						SourceLines.push_back(Line+"\n");
					
					GLchar ** ShaderSourceLinesArray = NULL;
					JENEW_A(ShaderSourceLinesArray,GLchar*,SourceLines.size())
					GLint * SourceLength = NULL;
					JENEW_A(SourceLength,GLint,SourceLines.size())

					s32 N = 0;
					for(std::vector<std::string>::iterator Iter = SourceLines.begin();Iter!=SourceLines.end();Iter++)
					{
						ShaderSourceLinesArray[N] = const_cast<GLchar*>((*Iter).c_str());
						SourceLength[N] = (GLint)(*Iter).length();
						N++;
					}

					Extensions->SetShaderSource(Shader,(GLsizei)SourceLines.size(),(const GLchar**)ShaderSourceLinesArray,SourceLength);

					JEDELETE_A(SourceLength)
					JEDELETE_A(ShaderSourceLinesArray)
				#else
					GLint SourceLength = (GLint)strlen(ShaderSource);
					Extensions->SetShaderSource(Shader,1,(const GLchar**)&ShaderSource,&SourceLength);
				#endif

				Extensions->CompileShader(Shader);

				s32 Compiled = 0;
				Extensions->GetShaderInfoState(Shader,GL_COMPILE_STATUS,&Compiled);

				if(!Compiled)
				{
					c8 LogInfo[MAX_LOG_SIZE];
					memset(LogInfo,0,MAX_LOG_SIZE);
					Extensions->GetShaderInfoLog(Shader,MAX_LOG_SIZE,NULL,LogInfo);
					JELOG_E(jeStringc("Cant compile shader : ")+jeStringc(LogInfo))

					#ifdef JE_SHADER_DEBUG
						JELOG_E("-------------------- BEGIN SHADER SOURCE --------------------")
						JELOG_E("----- NVIDIA first line is 1, ! ATI first line IS 0 ! --------------------")
						s32 N = 1;
						for(std::vector<std::string>::iterator Iter = SourceLines.begin();Iter!=SourceLines.end();Iter++)
						{
							jeStringc Temp = (*Iter).substr(0,(*Iter).size()-1).c_str();
							Temp += " // Ln : ";
							Temp += N++;
							JELOG(Temp)
						}
						JELOG_E("-------------------- END SHADER SOURCE --------------------")
					#endif
				}

				return Compiled ? true:false;
			}

			//! Compile Shader Program
			void CompileShaderProgram(const jeStringc & Source,ShaderDefinesListType * DefinesList);

			//! Remove Shader Progam
			void RemoveShaderProgram();

			//! OpenGL Work Mode
			OpenGLWorkMode CurrentWorkMode;
		public:
			//! Constructor
			CShaderMaterial_OpenGL(IRender * Render,CRender_OpenGL_Extensions * NewExtensions,const jeStringc & Source,ShaderDefinesListType * DefinesList,OpenGLWorkMode WorkMode);

			//! Destructor
			~CShaderMaterial_OpenGL();
			
			//! Get Variable
			IShaderVariable * GetVariable(const jeStringc & Name,u1 CreateNewInstance = false);

			//! Bind
			void Bind();

			//! UnBind
			void UnBind();

			//! Set Vertex Attribute
			void SetVertexAttribute(HardwareBufferArrayLayers Layer,u1 Enable,s8 VariablesPerObject = 0,GLenum VariableType = GL_NONE,u1 Normalize = false,s32 Stride = 0,const void * Pointer = NULL);

			//! Shader Material Internal Matrix Type
			enum ShaderMaterialInternalMatrixType
			{
				SMIMT_MODELVIEWPROJECTION,
				SMIMT_MODELVIEW,
				SMIMT_MODEL,
				SMIMT_NORMAL,
				SMIMT_TEXTURE
			};

			//! Set Matrixes
			void SetMatrixes(const jeMatrix4f & Matrix,ShaderMaterialInternalMatrixType Type);

			//! Need To Calculate Matrix
			u1 NeedToCalculateMatrix(ShaderMaterialInternalMatrixType Type);

			//! Bind Texture Layer
			void BindTextureLayer(GLuint Id,s32 LayerId);

			//! Recompile Shader Material
			void Recompile(ShaderDefinesListType * DefinesList);

			//! Remove Variable
			void RemoveVariable(CShaderVariable_OpenGL * Variable);

			//! Get Shader Setup Uniform List
			ShaderSetupUniformListType & GetShaderSetupUniformList();

			//! Is Binded
			u1 IsBinded();

			//! Is Variable Registered
			u1 IsVariableRegistered(const jeStringc & Name);

			//! Clear Uniform Setup List
			void ClearSetupUniformList();

			//! Set Ignore Location Error For New Variables
			void SetIgnoreLocationErrorForNewVariables(u1 Enabled);
		};
	}
}
#endif

#endif
